#pragma once

#include <string>
#include "util.h"
#include "request.h"
#include "response.h"


namespace evm { namespace http {

using namespace std;

struct Client {
    static constexpr const char* user_agent = "Evm HTTP Client";

    static Response get(string url, Headers headers = Headers(),
                        QueryParas query_paras = QueryParas(), size_t timeout = 0) {
        Request req;
        req.method = GET;
        req.url = std::move(url);
        req.headers = std::move(headers);
        req.query_paras = std::move(query_paras);
        Response res;
        request(req, res, timeout);
        return res;
    }

    static Response get(string url, Headers headers, size_t timeout = 0) {
        return get(std::move(url), headers, QueryParas(), timeout);
    }

    static Response get(string url, QueryParas query_paras, size_t timeout = 0) {
        return get(std::move(url), Headers(), query_paras, timeout);
    }

    static Response get(string url, size_t timeout) {
        return get(std::move(url), Headers(), QueryParas(), timeout);
    }

    static Response download(string url, Headers headers, string file_name, size_t timeout = 0) {
        Request req;
        req.method = GET;
        req.url = std::move(url);
        req.headers = std::move(headers);
        Response res;
        res.file = make_shared<File>(file_name, O_WRONLY | O_CREAT);
        request(req, res, timeout);
        return res;
    }

    static Response download(string url, string file_name, size_t timeout = 0) {
        return download(url, Headers(), file_name, timeout);
    }

    static Response post(string url, Headers headers, string data, size_t timeout = 0) {
        Request req;
        req.method = POST;
        req.url = std::move(url);
        req.headers = std::move(headers);
        req.body = std::move(data);
        Response res;
        request(req, res, timeout);
        return res;
    }

    static Response post(string url, Headers headers, Json json, size_t timeout = 0) {
        return post(std::move(url), std::move(headers), json.dump(), timeout);
    }

    static Response post(string url, string data, size_t timeout = 0) {
        return post(std::move(url), Headers(), data, timeout);
    }

    static Response post(string url, Json json, size_t timeout = 0) {
        return post(std::move(url), {{"Content-type", "application/json"}}, std::move(json), timeout);
    }

    static Response put(string url, Headers headers, string data, size_t timeout = 0) {
        Request req;
        req.method = PUT;
        req.url = std::move(url);
        req.headers = std::move(headers);
        req.body = std::move(data);
        Response res;
        request(req, res, timeout);
        return res;
    }

    static Response put(string url, Headers headers, Json json, size_t timeout = 0) {
        return put(std::move(url), std::move(headers), json.dump(), timeout);
    }

    static Response put(string url, string data, size_t timeout = 0) {
        return put(std::move(url), Headers(), data, timeout);
    }

    static Response put(string url, Json json, size_t timeout = 0) {
        return put(std::move(url), {{"Content-type", "application/json"}}, std::move(json), timeout);
    }

    static Response upload(string url, Headers headers, string file_name, size_t timeout = 0) {
        Request req;
        req.method = PUT;
        req.url = std::move(url);
        req.headers = std::move(headers);
        req.has_file = true;
        req.file_name = file_name;
        Response res;
        request(req, res, timeout);
        return res;
    }

    static Response del(string url, Headers headers, size_t timeout = 0) {
        Request req;
        req.method = DELETE;
        req.url = std::move(url);
        req.headers = std::move(headers);
        Response res;
        request(req, res, timeout);
        return res;
    }

    static Response del(string url, size_t timeout = 0) {
        return del(std::move(url), Headers(), timeout);
    }

    static void request(Request& req, Response& res, size_t timeout = 0);

private:
    struct ReadObject {
        const char* data{nullptr};
        File* file{nullptr};
        size_t len;
    };

    static size_t write_callback(void* data, size_t size, size_t nmemb, void* userdata);
    static size_t header_callback(void* data, size_t size, size_t nmemb, void* userdata);
    static size_t read_callback(void* data, size_t size, size_t nmemb, void* userdata);
};

}}
